#ifndef __M_DB_H__
#define __M_DB_H__
#include "util.hpp"
#include <mutex>
#include <cassert>

class user_table
{
private:
    MYSQL *_mysql;
    std::mutex _mutex;
public:
    user_table(const std::string &host, const std::string &username, const std::string &password,
               const std::string &dbname, uint16_t port = 3306)
    {
        _mysql = mysql_util::mysql_create(host.c_str(), username.c_str(), password.c_str(), dbname.c_str(), port);
        assert(_mysql);
    }
    ~user_table()
    {
        mysql_util::mysql_destroy(_mysql);
        _mysql = NULL;
    }
    bool insert(Json::Value &user) // 用户注册信息时，进行gobang数据库数据的新增
    {
        if(user["username"].isNull() || user["password"].isNull())
        {
            DLOG("please input complete username or password!!");
            return false;
        } 
        // 如果用户信息已经存在，则下面的sql语句会执行失败，mysqld会报错
#define INSERT_USER "insert into user values(null, '%s', SHA2('%s', 256), 1000, 0, 0);"
        char sql[4096] = {0};
        sprintf(sql, INSERT_USER, user["username"].asCString(), user["password"].asCString());
        bool ret = mysql_util::mysql_exec(_mysql, sql);
        if (ret == false)
        {
            DLOG("insert user info failed!!");
            return false;
        }
        return true;
    }
    bool login(Json::Value &user) // 登录验证 + 返回用户的其他详细信息(天梯分数，战斗场次，胜利场次)
    {
        // 以用户名作为查询的过滤条件，查询到数据存在则表示该用户已经注册过，然后再校验密码是否正确，如果正确则登录成功
#define LOGIN_USER "select id, score, total_count, win_count from user where username='%s' and password=SHA2('%s', 256);"
        char sql[4096] = {0};
        MYSQL_RES *res = NULL;
        sprintf(sql, LOGIN_USER, user["username"].asCString(), user["password"].asCString());

        //1.多个线程都是拿着同一个句柄对mysqld进行访问操作的，每个mysqlAPI单独执行是没问题的，但如果你想保证既能查询同时又能保存结果到本地，
        //  这就相当于你想让这两个操作合在一起是一个原子操作，所以只能加锁控制了
        //2.如果将查询和保存分开执行，当你想保存的时候，其他线程可能在执行其他的mysqlAPI了，那此时再执行store_result，有可能什么都拿不到了就，
        //  因为上一条执行的语句很有可能已经不是select，可能是其他线程执行的update，insert，delete语句等等
        //3.如果你想保存结果到本地，则必须保证上一个执行的sql语句是select，否则无法保存结果到本地，因此就只能加锁控制了
        {
            std::unique_lock<std::mutex> lock(_mutex);
            bool ret = mysql_util::mysql_exec(_mysql, sql);
            if (ret == false)
            {
                DLOG("user login failed!!");
                return false;
            }
            // 如果用户存在，则将已经存在的用户详细信息结果保存到本地，将信息赋值到输出型参数user里面。
            // 如果用户不存在则直接返回false
            res = mysql_store_result(_mysql);
            if (res == NULL)
            {
                DLOG("user information does not exist!!");
                return false;
            }
        }
        int row_num = mysql_num_rows(res);
        if (row_num > 1)
        {
            DLOG("user information does not unique!!");
            return false;
        }
        else if(row_num < 1)
        {
            DLOG("user information does not exists!!");
            return false;           
        }
        MYSQL_ROW row = mysql_fetch_row(res);

        user["id"] = std::stoi(row[0]);
        user["score"] = std::stoi(row[1]);
        user["total_count"] = std::stoi(row[2]);
        user["win_count"] = std::stoi(row[3]);
        mysql_free_result(res); // 释放结果集
        return true;
    }
    bool select_by_name(const std::string &name, Json::Value &user) // 通过用户名获取用户其他详细信息
    {
#define USER_BY_NAME "select id, score, total_count, win_count from user where username='%s';"
        char sql[4096] = {0};
        sprintf(sql, USER_BY_NAME, name.c_str());
        MYSQL_RES *res = NULL;
        {
            std::unique_lock<std::mutex> lock(_mutex);
            bool ret = mysql_util::mysql_exec(_mysql, sql);
            if (ret == false)
            {
                DLOG("failed to get user information by user name!!");
                return false;
            }
            res = mysql_store_result(_mysql);
            if (res == NULL)
            {
                DLOG("user information does not exist!!");
                return false;
            }
        }
        int row_num = mysql_num_rows(res);
        if (row_num > 1)
        {
            DLOG("user information does not unique!!");
            return false;
        }
        else if(row_num < 1)
        {
            DLOG("user information does not exists!!");
            return false;           
        }
        MYSQL_ROW row = mysql_fetch_row(res);
        user["id"] = std::stoi(row[0]);
        user["username"] = name;
        user["score"] = std::stoi(row[1]);
        user["total_count"] = std::stoi(row[2]);
        user["win_count"] = std::stoi(row[3]);
        mysql_free_result(res); // 释放结果集
        return true;
    }
    bool select_by_id(int id, Json::Value &user) // 通过用户id获取用户其他详细信息
    {
#define USER_BY_ID "select username, score, total_count, win_count from user where id=%d;"
        char sql[4096] = {0};
        sprintf(sql, USER_BY_ID, id);
        MYSQL_RES *res = NULL;
        {
            std::unique_lock<std::mutex> lock(_mutex);
            bool ret = mysql_util::mysql_exec(_mysql, sql);
            if (ret == false)
            {
                DLOG("failed to get user information by user id!!");
                return false;
            }
            res = mysql_store_result(_mysql);
            if (res == NULL)
            {
                DLOG("user information does not exist!!");
                return false;
            }
        }
        int row_num = mysql_num_rows(res);
        if (row_num > 1)
        {
            DLOG("user information does not unique!!");
            return false;
        }
        else if(row_num < 1)
        {
            DLOG("user information does not exists!!");
            return false;           
        }
        MYSQL_ROW row = mysql_fetch_row(res);
        user["id"] = id;
        user["username"] = row[0];
        user["score"] = std::stoi(row[1]);
        user["total_count"] = std::stoi(row[2]);
        user["win_count"] = std::stoi(row[3]);
        mysql_free_result(res); // 释放结果集
        return true;
    }
    bool win(int id)
    {
#define WIN "update user set score=score+30, total_count=total_count+1, win_count=win_count+1 where id=%d;"
        char sql[4096];
        sprintf(sql, WIN, id);
        bool ret = mysql_util::mysql_exec(_mysql, sql);
        if (ret == false)
        {
            DLOG("update win_user information failed!!");
            return false;
        }
        return true;
    }
    bool lose(int id)
    {
#define LOSE "update user set score=score-30, total_count=total_count+1 where id=%d;"
        char sql[4096];
        sprintf(sql, LOSE, id);
        bool ret = mysql_util::mysql_exec(_mysql, sql);
        if (ret == false)
        {
            DLOG("update lose_user information failed!!");
            return false;
        }
        return true;
    }
};
#endif